Оптимизация производственных расходов металлургического комбината «Стальная птица»¶

Table of Contents

  • 1  Загрузка данных
    • 1.1  Загрузка датасетов
    • 1.2  Анализ загруженных датасетов
      • 1.2.1  Датасет data_arc_new
      • 1.2.2  Датасет data_bulk_new
      • 1.2.3  Датасет data_bulk_time_new
      • 1.2.4  Датасет data_gas_new
      • 1.2.5  Датасет data_temp_new
      • 1.2.6  Датасет data_wire_new
      • 1.2.7  Датасет data_wire_time_new
  • 2  Исследовательский анализ и предобработка данных
    • 2.1  Функции для анализа и предобработки данных
    • 2.2  Анализ и предобработка данных датасета data_arc_new
    • 2.3  Анализ и предобработка данных датасета data_bulk_new
    • 2.4  Анализ и предобработка данных датасета data_bulk_time_new
    • 2.5  Анализ и предобработка данных датасета data_gas_new
    • 2.6  Анализ и предобработка данных датасета data_temp_new
    • 2.7  Анализ и предобработка данных датасета data_wire_new
    • 2.8  Анализ и предобработка данных датасета data_wire_time_new
  • 3  Объединение данных
  • 4  Исследовательский анализ и предобработка данных объединённого датафрейма
  • 5  Подготовка данных
  • 6  Обучение моделей машинного обучения
  • 7  Выбор лучшей модели
    • 7.1  Проверка на адекватность
    • 7.2  Анализ важности признаков для лучшей модели
  • 8  Общий вывод

ЦЕЛЬ ПРОЕКТА: Построить модель, которая будет предсказывать температуру сплава.

Описание проекта: промышленность

Чтобы оптимизировать производственные расходы, металлургический комбинат «Стальная птица» решил уменьшить потребление электроэнергии на этапе обработки стали. Для этого комбинату нужно контролировать температуру сплава.

Ваша задача — построить модель, которая будет её предсказывать.

Описание данных

  1. Файл data_arc_new.csv - данные об электродах:
  • key — номер партии;
  • Начало нагрева дугой — время начала нагрева;
  • Конец нагрева дугой — время окончания нагрева;
  • Активная мощность — значение активной мощности;
  • Реактивная мощность — значение реактивной мощности.
  1. Файл data_bulk_new.csv - данные о подаче сыпучих материалов (объём):
  • key— номер партии;
  • Bulk 1 … Bulk 15 — объём подаваемого материала.
  1. Файл data_bulk_time_new.csv - данные о подаче сыпучих материалов (время):
  • key — номер партии;
  • Bulk 1 … Bulk 15 — время подачи материала.
  1. Файл data_gas_new.csv - данные о продувке сплава газом;
  • key — номер партии;
  • Газ 1 — объём подаваемого газа.
  1. Файл data_temp_new.csv - результаты измерения температуры:
  • key — номер партии;
  • Время замера — время замера;
  • Температура — значение температуры.
  1. Файл data_wire_new.csv - данные о проволочных материалах (объём):
  • key — номер партии;
  • Wire 1 … Wire 9 — объём подаваемых проволочных материалов.
  1. Файл data_wire_time_new.csv - данные о проволочных материалах (время):
  • key — номер партии;
  • Wire 1 … Wire 9 — время подачи проволочных материалов.

Во всех файлах столбец key содержит номер партии. В файлах может быть несколько строк с одинаковым значением key: они соответствуют разным итерациям обработки.

Загрузка библиотек

In [1]:
%%capture

%pip install -qq scikit-learn catboost seaborn shap phik

# Обновление scikit-learn (на случай, если уже установлен устаревший)
%pip install -q -U scikit-learn
In [2]:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import re
import phik
import multiprocessing

from IPython.display import display
import os

from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import mean_absolute_error

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import (
    StandardScaler, MinMaxScaler
)
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer

from sklearn.dummy import DummyRegressor
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from catboost import CatBoostRegressor

import warnings
warnings.filterwarnings('ignore')
In [3]:
# Настройки вывода графиков
plt.rcParams["axes.titlesize"] = 16  # Размер шрифта
plt.rcParams["axes.titleweight"] = "bold"  # Толщина шрифта
In [4]:
# Константы
RANDOM_STATE = 160625
TEST_SIZE = 0.25

Загрузка данных¶

Функции для загрузки и отображенияи информации о датасете

In [5]:
from pathlib import Path

class CSVLoader:
    """Загрузчик CSV-файлов с двумя шаблонами путей."""

    PATH_TEMPLATES = [
        Path("C:/Data-science/ds_csv/{}"),
        Path("/datasets/{}"),
    ]

    def __init__(self):
        self.datasets = {}

    def _find_csv_path(self, filename):
        """Ищет CSV-файл по шаблонам путей."""
        for template in self.PATH_TEMPLATES:
            path = template.with_name(filename)
            if path.exists():
                print(f"Файл найден: {path}")
                return path
        raise FileNotFoundError(
            f"Файл '{filename}' не найден по путям:\n" +
            "\n".join(str(template.with_name(filename)) for template in self.PATH_TEMPLATES)
        )

    def load_csv(self, name, filename, **kwargs):
        """Загружает один CSV-файл."""
        try:
            path = self._find_csv_path(filename)
            df = pd.read_csv(path, **kwargs)
            self.datasets[name] = df
            print(f"✅ '{name}' загружен ({filename})")
            return df
        except Exception as e:
            print(f"❌ Ошибка при загрузке '{name}': {e}")
            return None

    def load_many(self, files_config):
        """Загружает несколько CSV-файлов по конфигурации."""
        for name, filename in files_config.items():
            self.load_csv(name, filename)
        return self.datasets
In [6]:
# Определяем TERM_SIZE
try:
    TERM_SIZE = os.get_terminal_size()
except:
    TERM_SIZE = type('', (), {'columns': 80})()

def show_info(df):
    display(df.head(5))
    print()
    df.info()
    print('=' * TERM_SIZE.columns)
    print(f'\033[1mКоличество дубликатов: \033[0m{df.duplicated().sum()}')
    print('=' * TERM_SIZE.columns)
    print("\033[1mПроцент пропусков от всего датасета:\033[0m")
    print()
    formatted_output = df.isna().mean().sort_values(ascending=False).apply(lambda x: "{:.1%}".format(x))
    print(formatted_output) # 
    print('=' * TERM_SIZE.columns)
    print(f'\033[1mОписание:\033[0m ')
    display(df.describe())
    print('=' * TERM_SIZE.columns)
    print(f'\033[1mРазмер: \033[0m{df.shape}')

Загрузка датасетов¶

In [7]:
loader = CSVLoader()

# Словарь: ключ — имя датасета, значение — имя CSV-файла
files = {
    'data_arc_new': 'data_arc_new.csv',
    'data_bulk_new': 'data_bulk_new.csv',
    'data_bulk_time_new': 'data_bulk_time_new.csv',
    'data_gas_new': 'data_gas_new.csv',
    'data_temp_new': 'data_temp_new.csv',
    'data_wire_new': 'data_wire_new.csv',
    'data_wire_time_new': 'data_wire_time_new.csv'
}

all_data = loader.load_many(files)
print("Загружено:", list(all_data.keys()))
Файл найден: C:\Data-science\ds_csv\data_arc_new.csv
✅ 'data_arc_new' загружен (data_arc_new.csv)
Файл найден: C:\Data-science\ds_csv\data_bulk_new.csv
✅ 'data_bulk_new' загружен (data_bulk_new.csv)
Файл найден: C:\Data-science\ds_csv\data_bulk_time_new.csv
✅ 'data_bulk_time_new' загружен (data_bulk_time_new.csv)
Файл найден: C:\Data-science\ds_csv\data_gas_new.csv
✅ 'data_gas_new' загружен (data_gas_new.csv)
Файл найден: C:\Data-science\ds_csv\data_temp_new.csv
✅ 'data_temp_new' загружен (data_temp_new.csv)
Файл найден: C:\Data-science\ds_csv\data_wire_new.csv
✅ 'data_wire_new' загружен (data_wire_new.csv)
Файл найден: C:\Data-science\ds_csv\data_wire_time_new.csv
✅ 'data_wire_time_new' загружен (data_wire_time_new.csv)
Загружено: ['data_arc_new', 'data_bulk_new', 'data_bulk_time_new', 'data_gas_new', 'data_temp_new', 'data_wire_new', 'data_wire_time_new']
In [8]:
# Создание переменных с именами как в словаре
for name, df in all_data.items():
    globals()[name] = df 

Анализ загруженных датасетов¶

Датасет data_arc_new¶

In [9]:
show_info(data_arc_new)
key Начало нагрева дугой Конец нагрева дугой Активная мощность Реактивная мощность
0 1 2019-05-03 11:02:14 2019-05-03 11:06:02 0.305130 0.211253
1 1 2019-05-03 11:07:28 2019-05-03 11:10:33 0.765658 0.477438
2 1 2019-05-03 11:11:44 2019-05-03 11:14:36 0.580313 0.430460
3 1 2019-05-03 11:18:14 2019-05-03 11:24:19 0.518496 0.379979
4 1 2019-05-03 11:26:09 2019-05-03 11:28:37 0.867133 0.643691
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 14876 entries, 0 to 14875
Data columns (total 5 columns):
 #   Column                Non-Null Count  Dtype  
---  ------                --------------  -----  
 0   key                   14876 non-null  int64  
 1   Начало нагрева дугой  14876 non-null  object 
 2   Конец нагрева дугой   14876 non-null  object 
 3   Активная мощность     14876 non-null  float64
 4   Реактивная мощность   14876 non-null  float64
dtypes: float64(2), int64(1), object(2)
memory usage: 581.2+ KB
========================================================================================================================
Количество дубликатов: 0
========================================================================================================================
Процент пропусков от всего датасета:

key                     0.0%
Начало нагрева дугой    0.0%
Конец нагрева дугой     0.0%
Активная мощность       0.0%
Реактивная мощность     0.0%
dtype: object
========================================================================================================================
Описание: 
key Активная мощность Реактивная мощность
count 14876.000000 14876.000000 14876.000000
mean 1615.220422 0.662752 0.438986
std 934.571502 0.258885 5.873485
min 1.000000 0.223120 -715.479924
25% 806.000000 0.467115 0.337175
50% 1617.000000 0.599587 0.441639
75% 2429.000000 0.830070 0.608201
max 3241.000000 1.463773 1.270284
========================================================================================================================
Размер: (14876, 5)

Вывод по загрузке датасета data_arc_new

  • В датасете 14876 строк и 5 столбцов;
  • Параметр key повторяется, что говорит о многократном нагреве одной партии;
  • Названия столбцов не соответствуют PEP8;
  • Данные времени о начале и окончании нагрева дугой имеют формат отличный от datetime;
  • В датасете отсутствуют пропуски;
  • В датасете отсутствуют дубликаты;
  • Минимальное значение реактивной мощности меньше 0 - явная аномалия требующая удаления.

Датасет data_bulk_new¶

In [10]:
show_info(data_bulk_new)
key Bulk 1 Bulk 2 Bulk 3 Bulk 4 Bulk 5 Bulk 6 Bulk 7 Bulk 8 Bulk 9 Bulk 10 Bulk 11 Bulk 12 Bulk 13 Bulk 14 Bulk 15
0 1 NaN NaN NaN 43.0 NaN NaN NaN NaN NaN NaN NaN 206.0 NaN 150.0 154.0
1 2 NaN NaN NaN 73.0 NaN NaN NaN NaN NaN NaN NaN 206.0 NaN 149.0 154.0
2 3 NaN NaN NaN 34.0 NaN NaN NaN NaN NaN NaN NaN 205.0 NaN 152.0 153.0
3 4 NaN NaN NaN 81.0 NaN NaN NaN NaN NaN NaN NaN 207.0 NaN 153.0 154.0
4 5 NaN NaN NaN 78.0 NaN NaN NaN NaN NaN NaN NaN 203.0 NaN 151.0 152.0
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3129 entries, 0 to 3128
Data columns (total 16 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   key      3129 non-null   int64  
 1   Bulk 1   252 non-null    float64
 2   Bulk 2   22 non-null     float64
 3   Bulk 3   1298 non-null   float64
 4   Bulk 4   1014 non-null   float64
 5   Bulk 5   77 non-null     float64
 6   Bulk 6   576 non-null    float64
 7   Bulk 7   25 non-null     float64
 8   Bulk 8   1 non-null      float64
 9   Bulk 9   19 non-null     float64
 10  Bulk 10  176 non-null    float64
 11  Bulk 11  177 non-null    float64
 12  Bulk 12  2450 non-null   float64
 13  Bulk 13  18 non-null     float64
 14  Bulk 14  2806 non-null   float64
 15  Bulk 15  2248 non-null   float64
dtypes: float64(15), int64(1)
memory usage: 391.2 KB
========================================================================================================================
Количество дубликатов: 0
========================================================================================================================
Процент пропусков от всего датасета:

Bulk 8     100.0%
Bulk 13     99.4%
Bulk 9      99.4%
Bulk 2      99.3%
Bulk 7      99.2%
Bulk 5      97.5%
Bulk 10     94.4%
Bulk 11     94.3%
Bulk 1      91.9%
Bulk 6      81.6%
Bulk 4      67.6%
Bulk 3      58.5%
Bulk 15     28.2%
Bulk 12     21.7%
Bulk 14     10.3%
key          0.0%
dtype: object
========================================================================================================================
Описание: 
key Bulk 1 Bulk 2 Bulk 3 Bulk 4 Bulk 5 Bulk 6 Bulk 7 Bulk 8 Bulk 9 Bulk 10 Bulk 11 Bulk 12 Bulk 13 Bulk 14 Bulk 15
count 3129.000000 252.000000 22.000000 1298.000000 1014.000000 77.000000 576.000000 25.000000 1.0 19.000000 176.000000 177.000000 2450.000000 18.000000 2806.000000 2248.000000
mean 1624.383509 39.242063 253.045455 113.879045 104.394477 107.025974 118.925347 305.600000 49.0 76.315789 83.284091 76.819209 260.471020 181.111111 170.284747 160.513345
std 933.337642 18.277654 21.180578 75.483494 48.184126 81.790646 72.057776 191.022904 NaN 21.720581 26.060347 59.655365 120.649269 46.088009 65.868652 51.765319
min 1.000000 10.000000 228.000000 6.000000 12.000000 11.000000 17.000000 47.000000 49.0 63.000000 24.000000 8.000000 53.000000 151.000000 16.000000 1.000000
25% 816.000000 27.000000 242.000000 58.000000 72.000000 70.000000 69.750000 155.000000 49.0 66.000000 64.000000 25.000000 204.000000 153.250000 119.000000 105.000000
50% 1622.000000 31.000000 251.500000 97.500000 102.000000 86.000000 100.000000 298.000000 49.0 68.000000 86.500000 64.000000 208.000000 155.500000 151.000000 160.000000
75% 2431.000000 46.000000 257.750000 152.000000 133.000000 132.000000 157.000000 406.000000 49.0 70.500000 102.000000 106.000000 316.000000 203.500000 205.750000 205.000000
max 3241.000000 185.000000 325.000000 454.000000 281.000000 603.000000 503.000000 772.000000 49.0 147.000000 159.000000 313.000000 1849.000000 305.000000 636.000000 405.000000
========================================================================================================================
Размер: (3129, 16)

Вывод по загрузке датасета data_bulk_new

  • В датасете 3129 строк и 16 столбцов;
  • Названия столбцов не соответствуют PEP8;
  • В датасете отсутствуют дубликаты;
  • В датасете большое количество пропусков.

Датасет data_bulk_time_new¶

In [11]:
show_info(data_bulk_time_new)
key Bulk 1 Bulk 2 Bulk 3 Bulk 4 Bulk 5 Bulk 6 Bulk 7 Bulk 8 Bulk 9 Bulk 10 Bulk 11 Bulk 12 Bulk 13 Bulk 14 Bulk 15
0 1 NaN NaN NaN 2019-05-03 11:28:48 NaN NaN NaN NaN NaN NaN NaN 2019-05-03 11:24:31 NaN 2019-05-03 11:14:50 2019-05-03 11:10:43
1 2 NaN NaN NaN 2019-05-03 11:36:50 NaN NaN NaN NaN NaN NaN NaN 2019-05-03 11:53:30 NaN 2019-05-03 11:48:37 2019-05-03 11:44:39
2 3 NaN NaN NaN 2019-05-03 12:32:39 NaN NaN NaN NaN NaN NaN NaN 2019-05-03 12:27:13 NaN 2019-05-03 12:21:01 2019-05-03 12:16:16
3 4 NaN NaN NaN 2019-05-03 12:43:22 NaN NaN NaN NaN NaN NaN NaN 2019-05-03 12:58:00 NaN 2019-05-03 12:51:11 2019-05-03 12:46:36
4 5 NaN NaN NaN 2019-05-03 13:30:47 NaN NaN NaN NaN NaN NaN NaN 2019-05-03 13:30:47 NaN 2019-05-03 13:34:12 2019-05-03 13:30:47
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3129 entries, 0 to 3128
Data columns (total 16 columns):
 #   Column   Non-Null Count  Dtype 
---  ------   --------------  ----- 
 0   key      3129 non-null   int64 
 1   Bulk 1   252 non-null    object
 2   Bulk 2   22 non-null     object
 3   Bulk 3   1298 non-null   object
 4   Bulk 4   1014 non-null   object
 5   Bulk 5   77 non-null     object
 6   Bulk 6   576 non-null    object
 7   Bulk 7   25 non-null     object
 8   Bulk 8   1 non-null      object
 9   Bulk 9   19 non-null     object
 10  Bulk 10  176 non-null    object
 11  Bulk 11  177 non-null    object
 12  Bulk 12  2450 non-null   object
 13  Bulk 13  18 non-null     object
 14  Bulk 14  2806 non-null   object
 15  Bulk 15  2248 non-null   object
dtypes: int64(1), object(15)
memory usage: 391.2+ KB
========================================================================================================================
Количество дубликатов: 0
========================================================================================================================
Процент пропусков от всего датасета:

Bulk 8     100.0%
Bulk 13     99.4%
Bulk 9      99.4%
Bulk 2      99.3%
Bulk 7      99.2%
Bulk 5      97.5%
Bulk 10     94.4%
Bulk 11     94.3%
Bulk 1      91.9%
Bulk 6      81.6%
Bulk 4      67.6%
Bulk 3      58.5%
Bulk 15     28.2%
Bulk 12     21.7%
Bulk 14     10.3%
key          0.0%
dtype: object
========================================================================================================================
Описание: 
key
count 3129.000000
mean 1624.383509
std 933.337642
min 1.000000
25% 816.000000
50% 1622.000000
75% 2431.000000
max 3241.000000
========================================================================================================================
Размер: (3129, 16)

Вывод по загрузке датасета data_bulk_time_new

  • В датасете 3129 строк и 16 столбцов, аналогичное с датасетом с объемом подачи материалов data_bulk_new;
  • Названия столбцов не соответствуют PEP8;
  • Данные со временем имеют отличный от datetime формат;
  • В датасете отсутствуют дубликаты;
  • В датасете количество пропусков, аналогично с датасетом data_bulk_new.

Датасет data_gas_new¶

In [12]:
show_info(data_gas_new)
key Газ 1
0 1 29.749986
1 2 12.555561
2 3 28.554793
3 4 18.841219
4 5 5.413692
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3239 entries, 0 to 3238
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   key     3239 non-null   int64  
 1   Газ 1   3239 non-null   float64
dtypes: float64(1), int64(1)
memory usage: 50.7 KB
========================================================================================================================
Количество дубликатов: 0
========================================================================================================================
Процент пропусков от всего датасета:

key      0.0%
Газ 1    0.0%
dtype: object
========================================================================================================================
Описание: 
key Газ 1
count 3239.000000 3239.000000
mean 1621.861377 11.002062
std 935.386334 6.220327
min 1.000000 0.008399
25% 812.500000 7.043089
50% 1622.000000 9.836267
75% 2431.500000 13.769915
max 3241.000000 77.995040
========================================================================================================================
Размер: (3239, 2)

Вывод по загрузке датасета data_gas_new

  • В датасете 3239 строк и 2 столбца;
  • Названия столбцов не соответствуют PEP8;
  • В датасете отсутствуют дубликаты;
  • В датасете отсутствуют пропуски.

Датасет data_temp_new¶

In [13]:
show_info(data_temp_new)
key Время замера Температура
0 1 2019-05-03 11:02:04 1571.0
1 1 2019-05-03 11:07:18 1604.0
2 1 2019-05-03 11:11:34 1618.0
3 1 2019-05-03 11:18:04 1601.0
4 1 2019-05-03 11:25:59 1606.0
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 18092 entries, 0 to 18091
Data columns (total 3 columns):
 #   Column        Non-Null Count  Dtype  
---  ------        --------------  -----  
 0   key           18092 non-null  int64  
 1   Время замера  18092 non-null  object 
 2   Температура   14665 non-null  float64
dtypes: float64(1), int64(1), object(1)
memory usage: 424.2+ KB
========================================================================================================================
Количество дубликатов: 0
========================================================================================================================
Процент пропусков от всего датасета:

Температура     18.9%
key              0.0%
Время замера     0.0%
dtype: object
========================================================================================================================
Описание: 
key Температура
count 18092.000000 14665.000000
mean 1616.460977 1590.722741
std 934.641385 20.394381
min 1.000000 1191.000000
25% 807.750000 1580.000000
50% 1618.000000 1590.000000
75% 2429.000000 1599.000000
max 3241.000000 1705.000000
========================================================================================================================
Размер: (18092, 3)

Вывод по загрузке датасета data_temp_new

  • В датасете 18092 строки и 3 столбца;
  • Параметр key повторяется, что говорит о многократном нагреве одной партии;
  • Названия столбцов не соответствуют PEP8;
  • В датасете отсутствуют дубликаты;
  • В датасете присутствуют пропуски.

Датасет data_wire_new¶

In [14]:
show_info(data_wire_new)
key Wire 1 Wire 2 Wire 3 Wire 4 Wire 5 Wire 6 Wire 7 Wire 8 Wire 9
0 1 60.059998 NaN NaN NaN NaN NaN NaN NaN NaN
1 2 96.052315 NaN NaN NaN NaN NaN NaN NaN NaN
2 3 91.160157 NaN NaN NaN NaN NaN NaN NaN NaN
3 4 89.063515 NaN NaN NaN NaN NaN NaN NaN NaN
4 5 89.238236 9.11456 NaN NaN NaN NaN NaN NaN NaN
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3081 entries, 0 to 3080
Data columns (total 10 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   key     3081 non-null   int64  
 1   Wire 1  3055 non-null   float64
 2   Wire 2  1079 non-null   float64
 3   Wire 3  63 non-null     float64
 4   Wire 4  14 non-null     float64
 5   Wire 5  1 non-null      float64
 6   Wire 6  73 non-null     float64
 7   Wire 7  11 non-null     float64
 8   Wire 8  19 non-null     float64
 9   Wire 9  29 non-null     float64
dtypes: float64(9), int64(1)
memory usage: 240.8 KB
========================================================================================================================
Количество дубликатов: 0
========================================================================================================================
Процент пропусков от всего датасета:

Wire 5    100.0%
Wire 7     99.6%
Wire 4     99.5%
Wire 8     99.4%
Wire 9     99.1%
Wire 3     98.0%
Wire 6     97.6%
Wire 2     65.0%
Wire 1      0.8%
key         0.0%
dtype: object
========================================================================================================================
Описание: 
key Wire 1 Wire 2 Wire 3 Wire 4 Wire 5 Wire 6 Wire 7 Wire 8 Wire 9
count 3081.000000 3055.000000 1079.000000 63.000000 14.000000 1.000 73.000000 11.000000 19.000000 29.000000
mean 1623.426485 100.895853 50.577323 189.482681 57.442841 15.132 48.016974 10.039007 53.625193 34.155752
std 932.996726 42.012518 39.320216 99.513444 28.824667 NaN 33.919845 8.610584 16.881728 19.931616
min 1.000000 1.918800 0.030160 0.144144 24.148801 15.132 0.034320 0.234208 45.076721 4.622800
25% 823.000000 72.115684 20.193680 95.135044 40.807002 15.132 25.053600 6.762756 46.094879 22.058401
50% 1619.000000 100.158234 40.142956 235.194977 45.234282 15.132 42.076324 9.017009 46.279999 30.066399
75% 2434.000000 126.060483 70.227558 276.252014 76.124619 15.132 64.212723 11.886057 48.089603 43.862003
max 3241.000000 330.314424 282.780152 385.008668 113.231044 15.132 180.454575 32.847674 102.762401 90.053604
========================================================================================================================
Размер: (3081, 10)

Вывод по загрузке датасета data_wire_new

  • В датасете 3081 строка и 10 столбцов;
  • Названия столбцов не соответствуют PEP8;
  • В датасете отсутствуют дубликаты;
  • В датасете присутствуют пропуски.

Датасет data_wire_time_new¶

In [15]:
show_info(data_wire_time_new)
key Wire 1 Wire 2 Wire 3 Wire 4 Wire 5 Wire 6 Wire 7 Wire 8 Wire 9
0 1 2019-05-03 11:06:19 NaN NaN NaN NaN NaN NaN NaN NaN
1 2 2019-05-03 11:36:50 NaN NaN NaN NaN NaN NaN NaN NaN
2 3 2019-05-03 12:11:46 NaN NaN NaN NaN NaN NaN NaN NaN
3 4 2019-05-03 12:43:22 NaN NaN NaN NaN NaN NaN NaN NaN
4 5 2019-05-03 13:20:44 2019-05-03 13:15:34 NaN NaN NaN NaN NaN NaN NaN
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3081 entries, 0 to 3080
Data columns (total 10 columns):
 #   Column  Non-Null Count  Dtype 
---  ------  --------------  ----- 
 0   key     3081 non-null   int64 
 1   Wire 1  3055 non-null   object
 2   Wire 2  1079 non-null   object
 3   Wire 3  63 non-null     object
 4   Wire 4  14 non-null     object
 5   Wire 5  1 non-null      object
 6   Wire 6  73 non-null     object
 7   Wire 7  11 non-null     object
 8   Wire 8  19 non-null     object
 9   Wire 9  29 non-null     object
dtypes: int64(1), object(9)
memory usage: 240.8+ KB
========================================================================================================================
Количество дубликатов: 0
========================================================================================================================
Процент пропусков от всего датасета:

Wire 5    100.0%
Wire 7     99.6%
Wire 4     99.5%
Wire 8     99.4%
Wire 9     99.1%
Wire 3     98.0%
Wire 6     97.6%
Wire 2     65.0%
Wire 1      0.8%
key         0.0%
dtype: object
========================================================================================================================
Описание: 
key
count 3081.000000
mean 1623.426485
std 932.996726
min 1.000000
25% 823.000000
50% 1619.000000
75% 2434.000000
max 3241.000000
========================================================================================================================
Размер: (3081, 10)

Вывод по загрузке датасета data_wire_time_new

  • В датасете 3081 строка и 10 столбцов;
  • Названия столбцов не соответствуют PEP8;
  • В датасете отсутствуют дубликаты;
  • В датасете пропуски аналогичны датасету data_wire_new.

Вывод

  • В датасетах столбцы на разных языках и много пропусков, принято решение пока пропуски оставим как есть.
  • Во всех столбцах датафреймов столбцы с датами не соответствуют формату datetime.

Пропуски в столбцах могут возникать по разным причинам: из-за ошибок при передаче данных, сбоев в работе датчиков или отсутствия необходимости в выполнении определённых операций (например, нагрева или добавления присадок).

Исследовательский анализ и предобработка данных¶

Функции для анализа и предобработки данных¶

In [16]:
def convert_to_pep8_column_names(df: pd.DataFrame) -> pd.DataFrame:
    '''
    Приводит названия столбцов датафрейма к snake_case (PEP 8).
    (работает только для англоязычных названий столбцов)
    '''
    
    new_columns = []
    for col in df.columns:
        # Заменяем пробелы и спецсимволы на "_"
        col = re.sub(r'[\s\-+.,!@#$%^&*()]', '_', str(col))
        # Разделяем CamelCase (например, "UserId" → "user_id")
        col = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', col)
        # Приводим к нижнему регистру и убираем повторяющиеся "_"
        col = col.lower().replace('__', '_').strip('_')
        # Удаляем последнее нижнее подчеркивание "_"
        col = col.rstrip('_')
        new_columns.append(col)
    
    # Возвращаем датафрейм с новыми названиями
    return df.rename(columns=dict(zip(df.columns, new_columns)))
In [17]:
def drop_high_na_columns(df: pd.DataFrame, threshold: float = 0.85) -> pd.DataFrame:
    """
    Удаляет столбцы из DataFrame, в которых доля пропущенных значений >= threshold.
    """

    # Вычисляем долю пропущенных значений для каждого столбца
    na_fraction = df.isna().mean()
    
    # Выбираем индексы столбцов, где доля NA >= threshold
    columns_to_drop = na_fraction[na_fraction >= threshold].index
    
    # Возвращаем новый DataFrame с удаленными столбцами
    return df.drop(columns=columns_to_drop)
In [18]:
def show_num_variable(df, column, target=None):
    '''
    Функция отображает гистограмму распределения
    и диаграмму размаха для числового столбца датафрейма,
    с возможной группировкой по переменной target.
    '''

    sns.set(style='whitegrid')
    f, axes = plt.subplots(2, 1, figsize=(14, 10), gridspec_kw={'height_ratios': [3, 1]})  # 2 строки, 1 столбец

    # Подготовка данных (удаление NaN)
    cols = [column] + ([target] if target is not None else [])
    plot_df = df[cols].dropna()

    # Гистограмма
    axes[0].set_title(f'Гистограмма распределения {column}', fontsize=16)
    axes[0].set_ylabel('Доля наблюдений', fontsize=14)
    axes[0].set_xlabel(column, fontsize=14)
    sns.histplot(
        data=plot_df,
        x=column,
        hue=target if target is not None else None,
        bins=20,
        kde=True,
        stat='probability',
        ax=axes[0]
    )
    # Добавляем линии среднего и медианы
    median = plot_df[column].median()
    mean = plot_df[column].mean()
    axes[0].axvline(median, color='red', linestyle='--', linewidth=2, label=f'Медиана: {median:.2f}')
    axes[0].axvline(mean, color='blue', linestyle='-', linewidth=2, label=f'Среднее: {mean:.2f}')
    axes[0].legend(fontsize=12)

    # Диаграмма размаха
    axes[1].set_title(f'Диаграмма размаха для {column}', fontsize=16)
    axes[1].set_ylabel(column, fontsize=14)
    
    if target is not None:
        sns.boxplot(data=plot_df, y=target, x=column, orient='h', ax=axes[1])
        axes[1].set_ylabel(target, fontsize=14)
        axes[1].set_xlabel(column, fontsize=14)
    else:
        sns.boxplot(data=plot_df, x=column, orient='h', ax=axes[1])
        axes[1].set_ylabel('')
        axes[1].set_xlabel(column, fontsize=14)

    plt.tight_layout()
    plt.show()

Анализ и предобработка данных датасета data_arc_new¶

In [19]:
data_arc_new.head(1)
Out[19]:
key Начало нагрева дугой Конец нагрева дугой Активная мощность Реактивная мощность
0 1 2019-05-03 11:02:14 2019-05-03 11:06:02 0.30513 0.211253

Приведем названия столбцов к PEP8 и время в формат datetime

In [20]:
data_arc_new.columns = ['key', 'start_heating','end_heating', 'active_power', 'reactive_power']

# Проверка
data_arc_new.columns
Out[20]:
Index(['key', 'start_heating', 'end_heating', 'active_power',
       'reactive_power'],
      dtype='object')

Изменим тип данных для столбцов start_heating и end_heating

In [21]:
cols_to_convert = ['start_heating','end_heating']

for col in cols_to_convert:
    data_arc_new[col] = pd.to_datetime(data_arc_new[col], errors='coerce')

# Проверка
data_arc_new.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 14876 entries, 0 to 14875
Data columns (total 5 columns):
 #   Column          Non-Null Count  Dtype         
---  ------          --------------  -----         
 0   key             14876 non-null  int64         
 1   start_heating   14876 non-null  datetime64[ns]
 2   end_heating     14876 non-null  datetime64[ns]
 3   active_power    14876 non-null  float64       
 4   reactive_power  14876 non-null  float64       
dtypes: datetime64[ns](2), float64(2), int64(1)
memory usage: 581.2 KB

В ходе загрузки данных было замечено аномальное значение реактивной мощности меньшей 0, уберем все такие аномалии

In [22]:
data_arc_new = data_arc_new.query('reactive_power > 0')

Добавим параметр связывающий реактивную и активную мощность нагревательной цепи используя формулу векторной взаимосвязи:

image.png

In [23]:
data_arc_new['full_power'] = np.sqrt(data_arc_new['active_power']**2 + data_arc_new['reactive_power']**2)

Добавим параметр времени необходимого на нагрев в секундах

In [24]:
data_arc_new['heating_time'] = (data_arc_new['end_heating'] - data_arc_new['start_heating']).dt.seconds

# Проверка
data_arc_new.head(3)
Out[24]:
key start_heating end_heating active_power reactive_power full_power heating_time
0 1 2019-05-03 11:02:14 2019-05-03 11:06:02 0.305130 0.211253 0.371123 228
1 1 2019-05-03 11:07:28 2019-05-03 11:10:33 0.765658 0.477438 0.902319 185
2 1 2019-05-03 11:11:44 2019-05-03 11:14:36 0.580313 0.430460 0.722536 172

Сгруппируем данные по партиям, т.к. в задаче нам необходимо предсказывать финальную температуру после нагрева.

In [25]:
data_arc_new_gp = data_arc_new.groupby(by='key').sum()
display(data_arc_new_gp.head())
data_arc_new_gp.shape
active_power reactive_power full_power heating_time
key
1 3.036730 2.142821 3.718736 1098
2 2.139408 1.453357 2.588349 811
3 4.063641 2.937457 5.019223 655
4 2.706489 2.056992 3.400038 741
5 2.252950 1.687991 2.816980 869
Out[25]:
(3214, 4)

После группировки датасета по партиям и добавления новых параметров, получили датасет с 3214 строками и 4 столбцами

In [26]:
# Формирование списка столбцов с количественными признаками
num_variables_col = data_arc_new_gp.select_dtypes(include=['number']).columns.to_list()
num_variables_col
Out[26]:
['active_power', 'reactive_power', 'full_power', 'heating_time']
In [27]:
# Вывод графиков для количественных признаков датафрейма data_arc_new
for col in num_variables_col:
    show_num_variable(data_arc_new_gp, col)
    print('=' * TERM_SIZE.columns)
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
In [28]:
data_arc_new_gp.describe().round(3)
Out[28]:
active_power reactive_power full_power heating_time
count 3214.000 3214.000 3214.000 3214.000
mean 3.067 2.254 3.811 794.545
std 1.209 0.895 1.503 332.491
min 0.268 0.196 0.332 57.000
25% 2.235 1.631 2.775 571.000
50% 2.985 2.177 3.694 770.000
75% 3.775 2.788 4.697 983.000
max 12.376 8.949 15.288 4189.000

Вывод

Общие выводы по графикам:

  • Распределение не симметрично, а смещено вправо;
  • У них выраженный пик — большинство значений сосредоточено в узком диапазоне;
  • Длинный "хвост" тянется вправо т.е, иногда встречаются более крупные значения, но они редкие;
  • На графиках изображена правосторонняя асимметрия т.к. медиана < среднего.

Вывод по графикам для параметра active_power:
-- Среднее значение активной мощности необходимой для нагрева - 3.067, медиана - 2.985.

Вывод по графикам для параметра reactive_power:
-- Среднее значение реактивной мощности необходимой для нагрева - 2.254, медиана - 2.177.

Вывод по графикам для параметра full_power:
-- Среднее значение активной мощности необходимой для нагрева - 3.811, медиана - 3.694.

Вывод по графикам для параметра heating_time:
-- Среднее значение времени необходимой для нагрева - 794.5 с, медиана - 770 с.




Анализ и предобработка данных датасета data_bulk_new¶

В датасете были столбцы с бошльшим количеством пропусков, удалим те в которых содержание пропусков более 85%

In [29]:
data_bulk_new = drop_high_na_columns(data_bulk_new)

# Проверка
data_bulk_new.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3129 entries, 0 to 3128
Data columns (total 7 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   key      3129 non-null   int64  
 1   Bulk 3   1298 non-null   float64
 2   Bulk 4   1014 non-null   float64
 3   Bulk 6   576 non-null    float64
 4   Bulk 12  2450 non-null   float64
 5   Bulk 14  2806 non-null   float64
 6   Bulk 15  2248 non-null   float64
dtypes: float64(6), int64(1)
memory usage: 171.2 KB

Приведем название столбцов в датасете к формату PEP8

In [30]:
data_bulk_new = convert_to_pep8_column_names(data_bulk_new)

# Проверка
data_bulk_new.columns
Out[30]:
Index(['key', 'bulk_3', 'bulk_4', 'bulk_6', 'bulk_12', 'bulk_14', 'bulk_15'], dtype='object')
In [31]:
# Формирование списка столбцов с количественными признаками
num_variables_col = data_bulk_new.select_dtypes(include=['number']).columns.to_list()
num_variables_col.remove('key')
num_variables_col
Out[31]:
['bulk_3', 'bulk_4', 'bulk_6', 'bulk_12', 'bulk_14', 'bulk_15']
In [32]:
# Вывод графиков для количественных признаков датафрейма
for col in num_variables_col:
    show_num_variable(data_bulk_new, col)
    print('=' * TERM_SIZE.columns)
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
In [33]:
data_bulk_new.drop(columns=['key']).describe().round(3)
Out[33]:
bulk_3 bulk_4 bulk_6 bulk_12 bulk_14 bulk_15
count 1298.000 1014.000 576.000 2450.000 2806.000 2248.000
mean 113.879 104.394 118.925 260.471 170.285 160.513
std 75.483 48.184 72.058 120.649 65.869 51.765
min 6.000 12.000 17.000 53.000 16.000 1.000
25% 58.000 72.000 69.750 204.000 119.000 105.000
50% 97.500 102.000 100.000 208.000 151.000 160.000
75% 152.000 133.000 157.000 316.000 205.750 205.000
max 454.000 281.000 503.000 1849.000 636.000 405.000

Вывод

Вывод по графикам для параметра bulk_3:

  • Распределение не симметрично, а смещено вправо;
  • На графике имеется один пик — большинство значений сосредоточено в узком диапазоне;
  • Длинный "хвост" тянется вправо т.е, иногда встречаются более крупные значения (выбросы), но они редкие;
  • На графиках изображена правосторонняя асимметрия т.к. медиана < среднего.
  • Среднее значение объема подачи сыпучего материала bulk_3 необходимой для нагрева - 113.9, медиана - 97.5.

Вывод по графикам для параметра bulk_4:

  • Распределение не симметрично, а смещено вправо;
  • На графиках изображена правосторонняя асимметрия т.к. медиана < среднего.
  • Среднее значение объема подачи сыпучего материала bulk_4 необходимой для нагрева - 104.4, медиана - 102.

Вывод по графикам для параметра bulk_6:

  • Распределение не симметрично, а смещено вправо;
  • На графике имеется один пик — большинство значений сосредоточено в узком диапазоне;
  • Длинный "хвост" тянется вправо т.е, иногда встречаются более крупные значения (выбросы), но они редкие;
  • На графиках изображена правосторонняя асимметрия т.к. медиана < среднего.
  • Среднее значение объема подачи сыпучего материала bulk_6 необходимой для нагрева - 118.9, медиана - 100.

Вывод по графикам для параметра bulk_12:

  • Распределение не симметрично, а смещено вправо;
  • На графике имеется один пик — большинство значений сосредоточено в узком диапазоне;
  • Длинный "хвост" тянется вправо т.е, иногда встречаются более крупные значения (выбросы), также присутствует значение более 1750 похожее на аномалию (стоит уточнить у заказчика возможно ли теоретически такое значение и в случае подтверждения аномалии удалить данную запись);
  • На графиках изображена правосторонняя асимметрия т.к. медиана < среднего.
  • Среднее значение объема подачи сыпучего материала bulk_12 необходимой для нагрева - 260.5, медиана - 208.

Вывод по графикам для параметра bulk_14:

  • Распределение не симметрично, а смещено вправо;
  • Длинный "хвост" тянется вправо т.е, иногда встречаются более крупные значения;
  • На графиках изображена правосторонняя асимметрия т.к. медиана < среднего.
  • Среднее значение объема подачи сыпучего материала bulk_14 необходимой для нагрева - 170.3, медиана - 151.

Вывод по графикам для параметра bulk_15:

  • Наблюдается двумодальное распределение;
  • Среднее значение объема подачи сыпучего материала bulk_15 необходимой для нагрева - 160.5, медиана - 160, отсутствует сильная асимметрия – нет выраженного перекоса вправо или влево.

Анализ и предобработка данных датасета data_bulk_time_new¶

In [34]:
data_bulk_time_new.head(3)
Out[34]:
key Bulk 1 Bulk 2 Bulk 3 Bulk 4 Bulk 5 Bulk 6 Bulk 7 Bulk 8 Bulk 9 Bulk 10 Bulk 11 Bulk 12 Bulk 13 Bulk 14 Bulk 15
0 1 NaN NaN NaN 2019-05-03 11:28:48 NaN NaN NaN NaN NaN NaN NaN 2019-05-03 11:24:31 NaN 2019-05-03 11:14:50 2019-05-03 11:10:43
1 2 NaN NaN NaN 2019-05-03 11:36:50 NaN NaN NaN NaN NaN NaN NaN 2019-05-03 11:53:30 NaN 2019-05-03 11:48:37 2019-05-03 11:44:39
2 3 NaN NaN NaN 2019-05-03 12:32:39 NaN NaN NaN NaN NaN NaN NaN 2019-05-03 12:27:13 NaN 2019-05-03 12:21:01 2019-05-03 12:16:16

Приведем названия столбцов датасета к формату PEP8

In [35]:
data_bulk_time_new = convert_to_pep8_column_names(data_bulk_time_new)

# Проверка
data_bulk_time_new.columns
Out[35]:
Index(['key', 'bulk_1', 'bulk_2', 'bulk_3', 'bulk_4', 'bulk_5', 'bulk_6',
       'bulk_7', 'bulk_8', 'bulk_9', 'bulk_10', 'bulk_11', 'bulk_12',
       'bulk_13', 'bulk_14', 'bulk_15'],
      dtype='object')

Удалим столбцы содержащие большое количество пропусков (более 85%)

In [36]:
data_bulk_time_new = drop_high_na_columns(data_bulk_time_new)

# Проверка
data_bulk_time_new.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3129 entries, 0 to 3128
Data columns (total 7 columns):
 #   Column   Non-Null Count  Dtype 
---  ------   --------------  ----- 
 0   key      3129 non-null   int64 
 1   bulk_3   1298 non-null   object
 2   bulk_4   1014 non-null   object
 3   bulk_6   576 non-null    object
 4   bulk_12  2450 non-null   object
 5   bulk_14  2806 non-null   object
 6   bulk_15  2248 non-null   object
dtypes: int64(1), object(6)
memory usage: 171.2+ KB

Переведем столбцы со временем в формат datetime

In [37]:
cols_to_convert = data_bulk_time_new.select_dtypes(include=['object'])

for col in cols_to_convert:
    data_bulk_time_new[col] = pd.to_datetime(data_bulk_time_new[col], errors='coerce')

# Проверка
data_bulk_time_new.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3129 entries, 0 to 3128
Data columns (total 7 columns):
 #   Column   Non-Null Count  Dtype         
---  ------   --------------  -----         
 0   key      3129 non-null   int64         
 1   bulk_3   1298 non-null   datetime64[ns]
 2   bulk_4   1014 non-null   datetime64[ns]
 3   bulk_6   576 non-null    datetime64[ns]
 4   bulk_12  2450 non-null   datetime64[ns]
 5   bulk_14  2806 non-null   datetime64[ns]
 6   bulk_15  2248 non-null   datetime64[ns]
dtypes: datetime64[ns](6), int64(1)
memory usage: 171.2 KB

Вывод: для предсказания конечной температуры нагрева, время подачи сыпучих материалов не несет смысловой нагрузки, анализ данного датасета можно опуcтить.

Анализ и предобработка данных датасета data_gas_new¶

In [38]:
data_gas_new.head(1)
Out[38]:
key Газ 1
0 1 29.749986

Приведем название столбцов в датасете к формату PEP8

In [39]:
data_gas_new.columns = ['key', 'gas']
data_gas_new.columns 
Out[39]:
Index(['key', 'gas'], dtype='object')
In [40]:
show_num_variable(data_gas_new, 'gas')
No description has been provided for this image
In [41]:
data_gas_new['gas'].describe().round(3)
Out[41]:
count    3239.000
mean       11.002
std         6.220
min         0.008
25%         7.043
50%         9.836
75%        13.770
max        77.995
Name: gas, dtype: float64

Вывод

Вывод по графикам для параметра gas:

  • Распределение не симметрично, а смещено вправо;
  • На графике имеется один пик — большинство значений сосредоточено в узком диапазоне;
  • Длинный "хвост" тянется вправо т.е, иногда встречаются более крупные значения (выбросы), но они редкие;
  • На графиках изображена правосторонняя асимметрия т.к. медиана < среднего.
  • Среднее значение объема подачи газа gas необходимой для нагрева - 11, медиана - 9.836.

Анализ и предобработка данных датасета data_temp_new¶

In [42]:
data_temp_new.head(3)
Out[42]:
key Время замера Температура
0 1 2019-05-03 11:02:04 1571.0
1 1 2019-05-03 11:07:18 1604.0
2 1 2019-05-03 11:11:34 1618.0

Приведем название столбцов в датасете к формату PEP8

In [43]:
data_temp_new.columns = ['key', 'time', 'temperature']
data_temp_new.columns
Out[43]:
Index(['key', 'time', 'temperature'], dtype='object')

Приведем столбец с временем к формату datetime

In [44]:
data_temp_new['time'] = pd.to_datetime(data_temp_new['time'], errors='coerce')

# Проверка
data_temp_new.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 18092 entries, 0 to 18091
Data columns (total 3 columns):
 #   Column       Non-Null Count  Dtype         
---  ------       --------------  -----         
 0   key          18092 non-null  int64         
 1   time         18092 non-null  datetime64[ns]
 2   temperature  14665 non-null  float64       
dtypes: datetime64[ns](1), float64(1), int64(1)
memory usage: 424.2 KB

Проверим во всех ли партиях происходил замер температуры

In [45]:
summary = data_temp_new.groupby('key')['temperature'].agg(
    total_count='count',                  # количество НЕ NaN
    total_rows='size'                    # общее количество строк
)

# Ищем ключи, где ровно одно значение заполнено
keys_with_one_value = summary[summary['total_count'] == 1].index.tolist()

print("Ключи, где только одно значение temperature не пропущено:")
print(keys_with_one_value)
Ключи, где только одно значение temperature не пропущено:
[195, 279, 2500, 2501, 2502, 2503, 2504, 2505, 2506, 2507, 2508, 2509, 2510, 2511, 2512, 2513, 2514, 2515, 2516, 2517, 2518, 2519, 2520, 2521, 2522, 2523, 2524, 2525, 2526, 2527, 2528, 2529, 2530, 2531, 2532, 2533, 2534, 2535, 2536, 2537, 2538, 2539, 2540, 2541, 2542, 2543, 2544, 2545, 2546, 2547, 2548, 2549, 2550, 2551, 2552, 2553, 2554, 2555, 2556, 2557, 2558, 2559, 2560, 2561, 2562, 2563, 2564, 2565, 2566, 2567, 2568, 2569, 2570, 2571, 2572, 2573, 2574, 2575, 2576, 2577, 2578, 2579, 2580, 2581, 2582, 2583, 2584, 2585, 2586, 2587, 2588, 2589, 2590, 2591, 2592, 2593, 2594, 2595, 2596, 2597, 2598, 2599, 2600, 2601, 2602, 2603, 2604, 2605, 2606, 2607, 2608, 2609, 2610, 2611, 2612, 2613, 2614, 2615, 2616, 2617, 2618, 2619, 2620, 2621, 2622, 2623, 2624, 2625, 2626, 2627, 2628, 2629, 2630, 2631, 2632, 2633, 2634, 2635, 2636, 2637, 2638, 2639, 2640, 2641, 2642, 2643, 2644, 2645, 2646, 2647, 2648, 2649, 2650, 2651, 2652, 2653, 2654, 2655, 2656, 2657, 2658, 2659, 2660, 2661, 2662, 2663, 2664, 2665, 2666, 2667, 2668, 2669, 2670, 2671, 2672, 2673, 2674, 2675, 2676, 2677, 2678, 2679, 2680, 2681, 2682, 2684, 2685, 2686, 2687, 2688, 2689, 2690, 2691, 2692, 2693, 2694, 2695, 2696, 2697, 2698, 2699, 2700, 2701, 2702, 2703, 2704, 2705, 2706, 2707, 2708, 2709, 2710, 2711, 2712, 2713, 2714, 2715, 2716, 2717, 2718, 2719, 2720, 2721, 2722, 2723, 2724, 2725, 2726, 2727, 2728, 2729, 2730, 2731, 2732, 2733, 2734, 2735, 2736, 2737, 2738, 2739, 2740, 2741, 2742, 2743, 2744, 2745, 2746, 2747, 2748, 2749, 2750, 2751, 2752, 2753, 2754, 2755, 2756, 2757, 2758, 2759, 2760, 2761, 2762, 2763, 2764, 2765, 2766, 2767, 2768, 2769, 2770, 2771, 2772, 2773, 2774, 2775, 2776, 2777, 2778, 2779, 2780, 2781, 2782, 2783, 2784, 2785, 2786, 2787, 2788, 2789, 2790, 2791, 2792, 2793, 2794, 2795, 2796, 2797, 2798, 2799, 2800, 2801, 2802, 2803, 2804, 2805, 2806, 2807, 2808, 2809, 2810, 2811, 2812, 2813, 2814, 2815, 2816, 2817, 2818, 2819, 2820, 2821, 2822, 2823, 2824, 2825, 2826, 2827, 2828, 2829, 2830, 2831, 2832, 2833, 2834, 2835, 2836, 2837, 2838, 2839, 2840, 2841, 2842, 2843, 2844, 2845, 2846, 2847, 2848, 2849, 2850, 2851, 2852, 2853, 2854, 2855, 2856, 2857, 2858, 2859, 2860, 2861, 2862, 2863, 2864, 2865, 2866, 2867, 2868, 2869, 2870, 2871, 2872, 2873, 2874, 2875, 2876, 2877, 2878, 2879, 2880, 2881, 2882, 2883, 2884, 2885, 2886, 2887, 2888, 2889, 2890, 2891, 2892, 2893, 2894, 2895, 2896, 2897, 2898, 2899, 2900, 2901, 2902, 2903, 2904, 2905, 2906, 2907, 2908, 2909, 2910, 2911, 2912, 2913, 2914, 2915, 2916, 2917, 2918, 2919, 2920, 2921, 2922, 2923, 2924, 2925, 2926, 2927, 2928, 2929, 2930, 2931, 2932, 2933, 2934, 2935, 2936, 2937, 2938, 2939, 2940, 2941, 2942, 2943, 2944, 2945, 2946, 2947, 2948, 2949, 2950, 2951, 2952, 2953, 2954, 2955, 2956, 2957, 2958, 2959, 2960, 2961, 2962, 2963, 2964, 2965, 2966, 2967, 2968, 2969, 2970, 2971, 2972, 2973, 2974, 2975, 2976, 2977, 2978, 2979, 2980, 2981, 2982, 2983, 2984, 2985, 2986, 2987, 2988, 2989, 2990, 2991, 2992, 2993, 2994, 2995, 2996, 2997, 2998, 2999, 3000, 3001, 3002, 3003, 3004, 3005, 3006, 3007, 3008, 3009, 3010, 3011, 3012, 3013, 3014, 3015, 3016, 3017, 3018, 3019, 3020, 3021, 3022, 3023, 3024, 3025, 3026, 3027, 3028, 3029, 3030, 3031, 3032, 3033, 3034, 3035, 3036, 3037, 3038, 3039, 3040, 3041, 3042, 3043, 3044, 3045, 3046, 3047, 3048, 3049, 3050, 3051, 3052, 3053, 3054, 3055, 3056, 3057, 3058, 3059, 3060, 3061, 3062, 3063, 3064, 3065, 3066, 3067, 3068, 3069, 3070, 3071, 3072, 3073, 3074, 3075, 3076, 3077, 3078, 3079, 3080, 3081, 3082, 3083, 3084, 3085, 3086, 3087, 3088, 3089, 3090, 3091, 3092, 3093, 3094, 3095, 3096, 3097, 3098, 3099, 3100, 3101, 3102, 3103, 3104, 3105, 3106, 3107, 3108, 3109, 3110, 3111, 3112, 3113, 3114, 3115, 3116, 3117, 3118, 3119, 3120, 3121, 3122, 3123, 3124, 3125, 3126, 3127, 3128, 3129, 3130, 3131, 3132, 3133, 3134, 3135, 3136, 3137, 3138, 3139, 3140, 3141, 3142, 3143, 3144, 3145, 3146, 3147, 3148, 3149, 3150, 3151, 3152, 3153, 3154, 3155, 3156, 3157, 3158, 3159, 3160, 3161, 3162, 3163, 3164, 3165, 3166, 3167, 3168, 3169, 3170, 3171, 3172, 3173, 3174, 3175, 3176, 3177, 3178, 3179, 3180, 3181, 3182, 3183, 3184, 3185, 3186, 3187, 3188, 3189, 3190, 3191, 3192, 3193, 3194, 3195, 3196, 3197, 3198, 3199, 3201, 3202, 3203, 3204, 3205, 3206, 3208, 3209, 3210, 3211, 3212, 3213, 3214, 3215, 3216, 3217, 3218, 3219, 3220, 3221, 3222, 3223, 3224, 3225, 3226, 3227, 3228, 3229, 3230, 3231, 3232, 3233, 3234, 3235, 3236, 3237, 3238, 3239, 3240, 3241]
In [46]:
# Выберем подмножество ключей — например, первые 3 и последние 3
subset_keys = keys_with_one_value[:3] + keys_with_one_value[-3:]

# Проверим какие значения отфильтровались
filtered_data = data_temp_new[data_temp_new['key'].isin(subset_keys)]
print(filtered_data)
        key                time  temperature
1105    195 2019-05-11 00:01:36       1583.0
1549    279 2019-05-14 10:13:01       1603.0
13926  2500 2019-08-10 14:04:39       1539.0
13927  2500 2019-08-10 14:13:11          NaN
13928  2500 2019-08-10 14:18:12          NaN
13929  2500 2019-08-10 14:25:53          NaN
13930  2500 2019-08-10 14:29:39          NaN
18071  3239 2019-09-06 14:16:50       1598.0
18072  3239 2019-09-06 14:22:49          NaN
18073  3239 2019-09-06 14:28:54          NaN
18074  3239 2019-09-06 14:33:34          NaN
18075  3239 2019-09-06 14:42:48          NaN
18076  3239 2019-09-06 14:56:58          NaN
18077  3239 2019-09-06 14:59:25          NaN
18078  3239 2019-09-06 15:03:35          NaN
18079  3239 2019-09-06 15:09:55          NaN
18080  3240 2019-09-06 15:25:21       1617.0
18081  3240 2019-09-06 15:30:52          NaN
18082  3240 2019-09-06 15:58:35          NaN
18083  3240 2019-09-06 16:02:31          NaN
18084  3240 2019-09-06 16:21:44          NaN
18085  3240 2019-09-06 16:35:26          NaN
18086  3241 2019-09-06 16:48:55       1586.0
18087  3241 2019-09-06 16:55:01          NaN
18088  3241 2019-09-06 17:06:38          NaN
18089  3241 2019-09-06 17:21:48          NaN
18090  3241 2019-09-06 17:24:44          NaN
18091  3241 2019-09-06 17:30:05          NaN

В партиях 195, 279 и с 2500 по 3241 замер температуры на этапах обработки не производился, такие партии для задачи не подходят - удалим их

In [47]:
data_temp_filtered = data_temp_new[~data_temp_new['key'].isin(keys_with_one_value)]
data_temp_filtered['key'].unique()
Out[47]:
array([   1,    2,    3, ..., 2497, 2498, 2499], dtype=int64)

Сгруппируем данные по партиям

In [86]:
data_temp_gp = data_temp_filtered.groupby(by = 'key').agg(['first', 'last'])
data_temp_gp.columns = ['first_time', 'finish_time', 'first_temp', 'finish_temp']

# Проверка
display(data_temp_gp.head())
print('Размерность получившегося датасета')
data_temp_gp.shape
first_time finish_time first_temp finish_temp
key
1 2019-05-03 11:02:04 2019-05-03 11:30:38 1571.0 1613.0
2 2019-05-03 11:34:04 2019-05-03 11:55:09 1581.0 1602.0
3 2019-05-03 12:06:44 2019-05-03 12:35:57 1596.0 1599.0
4 2019-05-03 12:39:27 2019-05-03 12:59:47 1601.0 1625.0
5 2019-05-03 13:11:03 2019-05-03 13:36:39 1576.0 1602.0
Размерность получившегося датасета
Out[86]:
(2475, 4)

Добавим параметр суммарного времени необходимого для замера нагрева в секундах

In [49]:
data_temp_gp['time_diff'] = (
    data_temp_gp['finish_time'] - data_temp_gp['first_time']).dt.seconds

data_temp_gp.sample(5)
Out[49]:
first_time finish_time first_temp finish_temp time_diff
key
610 2019-05-26 12:54:34 2019-05-26 13:23:27 1611.0 1602.0 1733
1544 2019-06-30 15:31:26 2019-06-30 16:29:28 1591.0 1590.0 3482
998 2019-06-10 23:08:28 2019-06-10 23:30:28 1586.0 1586.0 1320
1412 2019-06-25 15:02:20 2019-06-25 15:37:44 1546.0 1575.0 2124
992 2019-06-10 18:27:09 2019-06-10 18:58:14 1632.0 1593.0 1865
In [50]:
# Формирование списка столбцов с количественными признаками
num_variables_col = data_temp_gp.select_dtypes(include=['number']).columns.to_list()
num_variables_col
Out[50]:
['first_temp', 'finish_temp', 'time_diff']
In [51]:
# Вывод графиков для количественных признаков датафрейма
for col in num_variables_col:
    show_num_variable(data_temp_gp, col)
    print('=' * TERM_SIZE.columns)
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
In [52]:
show_num_variable(
    data_temp_gp.assign(
        temp_diff=data_temp_gp['finish_temp'] - data_temp_gp['first_temp']),
    'temp_diff')
No description has been provided for this image
In [53]:
data_temp_gp.assign(
        temp_diff=data_temp_gp['finish_temp'] - data_temp_gp['first_temp']
        ).describe().round(3)
Out[53]:
first_temp finish_temp time_diff temp_diff
count 2475.000 2475.000 2475.000 2475.000
mean 1588.402 1595.336 2280.897 6.934
std 29.243 16.023 1373.706 27.549
min 1191.000 1541.000 339.000 -98.000
25% 1572.000 1587.000 1544.500 -8.000
50% 1588.000 1593.000 2009.000 7.000
75% 1605.000 1599.000 2738.500 22.000
max 1679.000 1700.000 23674.000 408.000

Вывод

Вывод по графикам для параметра first_temp:

  • На графике имеется один пик — большинство значений сосредоточено в узком диапазоне;
  • Длинный "хвост" тянется влево т.е, иногда встречаются маленькие значения (выбросы), но они редкие;
  • Среднее значение начальной температуры first_temp при обработке материала - 1588, медиана - 1588.

Вывод по графикам для параметра finish_temp:

  • На графиках изображена правосторонняя асимметрия т.к. медиана < среднего.
  • Среднее значение конечной температуры finish_temp при обработке материала - 1595, медиана - 1593.

Вывод по графикам для параметра time_diff:

  • Распределение не симметрично, а смещено вправо;
  • На графике имеется один пик — большинство значений сосредоточено в узком диапазоне;
  • Длинный "хвост" тянется вправо т.е, иногда встречаются более крупные значения (выбросы), но они редкие;
  • На графиках изображена правосторонняя асимметрия т.к. медиана < среднего.
  • Среднее значение времени необходимого на замер температуры при обработке time_diff - 2281, медиана - 2009.

Вывод по графикам для параметра temp_diff:

  • Распределение не симметрично, а смещено вправо;
  • На графике имеется один пик — большинство значений сосредоточено в узком диапазоне;
  • Среднее значение разности температуры между начальным и конечным этапом обработки time_diff - 6.934, медиана - 7.

Анализ и предобработка данных датасета data_wire_new¶

In [54]:
data_wire_new.head(3)
Out[54]:
key Wire 1 Wire 2 Wire 3 Wire 4 Wire 5 Wire 6 Wire 7 Wire 8 Wire 9
0 1 60.059998 NaN NaN NaN NaN NaN NaN NaN NaN
1 2 96.052315 NaN NaN NaN NaN NaN NaN NaN NaN
2 3 91.160157 NaN NaN NaN NaN NaN NaN NaN NaN

Приведем наименования столбцов датасета к формату PEP8

In [55]:
data_wire_new = convert_to_pep8_column_names(data_wire_new)

# Проверка
data_wire_new.columns
Out[55]:
Index(['key', 'wire_1', 'wire_2', 'wire_3', 'wire_4', 'wire_5', 'wire_6',
       'wire_7', 'wire_8', 'wire_9'],
      dtype='object')

Удалим столбцы содержащие большое количество пропусков (более 85%)

In [56]:
data_wire_new = drop_high_na_columns(data_wire_new)

# Проверка
data_wire_new.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3081 entries, 0 to 3080
Data columns (total 3 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   key     3081 non-null   int64  
 1   wire_1  3055 non-null   float64
 2   wire_2  1079 non-null   float64
dtypes: float64(2), int64(1)
memory usage: 72.3 KB
In [57]:
# Формирование списка столбцов с количественными признаками
num_variables_col = data_wire_new.select_dtypes(include=['number']).columns.to_list()
num_variables_col.remove('key')
num_variables_col
Out[57]:
['wire_1', 'wire_2']
In [58]:
# Вывод графиков для количественных признаков датафрейма
for col in num_variables_col:
    show_num_variable(data_wire_new, col)
    print('=' * TERM_SIZE.columns)
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
In [59]:
data_wire_new.drop(columns=['key']).describe().round(3)
Out[59]:
wire_1 wire_2
count 3055.000 1079.000
mean 100.896 50.577
std 42.013 39.320
min 1.919 0.030
25% 72.116 20.194
50% 100.158 40.143
75% 126.060 70.228
max 330.314 282.780

Вывод

Вывод по графикам для параметра wire_1:

  • На графике имеется один пик — большинство значений сосредоточено в узком диапазоне;
  • Длинный "хвост" тянется вправо т.е, иногда встречаются более крупные значения (выбросы), но они редкие;
  • Среднее значение объема подачи проволочного материала wire_1 при обработке - 100.9, медиана - 100.2.

Вывод по графикам для параметра wire_2:

  • Распределение не симметрично, а смещено вправо;
  • На графике имеется один пик — большинство значений сосредоточено в узком диапазоне;
  • Длинный "хвост" тянется вправо т.е, иногда встречаются более крупные значения (выбросы), но они редкие;
  • На графиках изображена правосторонняя асимметрия т.к. медиана < среднего.
  • Среднее значение объема подачи проволочного материала wire_2 при обработке - 50.58, медиана - 40.14.

Анализ и предобработка данных датасета data_wire_time_new¶

In [60]:
data_wire_time_new.head(3)
Out[60]:
key Wire 1 Wire 2 Wire 3 Wire 4 Wire 5 Wire 6 Wire 7 Wire 8 Wire 9
0 1 2019-05-03 11:06:19 NaN NaN NaN NaN NaN NaN NaN NaN
1 2 2019-05-03 11:36:50 NaN NaN NaN NaN NaN NaN NaN NaN
2 3 2019-05-03 12:11:46 NaN NaN NaN NaN NaN NaN NaN NaN

Приведем названия столбцов датасета к формату PEP8

In [61]:
data_wire_time_new = convert_to_pep8_column_names(data_wire_time_new)

# Проверка
data_wire_time_new.columns
Out[61]:
Index(['key', 'wire_1', 'wire_2', 'wire_3', 'wire_4', 'wire_5', 'wire_6',
       'wire_7', 'wire_8', 'wire_9'],
      dtype='object')

Удалим столбцы содержащие большое количество пропусков (более 85%)

In [62]:
data_wire_time_new = drop_high_na_columns(data_wire_time_new)

# Проверка
data_wire_time_new.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3081 entries, 0 to 3080
Data columns (total 3 columns):
 #   Column  Non-Null Count  Dtype 
---  ------  --------------  ----- 
 0   key     3081 non-null   int64 
 1   wire_1  3055 non-null   object
 2   wire_2  1079 non-null   object
dtypes: int64(1), object(2)
memory usage: 72.3+ KB

Переведем столбцы со временем в формат datetime

In [63]:
cols_to_convert = data_wire_time_new.select_dtypes(include=['object'])

for col in cols_to_convert:
    data_wire_time_new[col] = pd.to_datetime(data_wire_time_new[col], errors='coerce')

# Проверка
data_wire_time_new.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3081 entries, 0 to 3080
Data columns (total 3 columns):
 #   Column  Non-Null Count  Dtype         
---  ------  --------------  -----         
 0   key     3081 non-null   int64         
 1   wire_1  3055 non-null   datetime64[ns]
 2   wire_2  1079 non-null   datetime64[ns]
dtypes: datetime64[ns](2), int64(1)
memory usage: 72.3 KB

Вывод: для предсказания конечной температуры нагрева, время подачи проволочных материалов не несет смысловой нагрузки, анализ данного датасета можно опуcтить.

Вывод

  • Привели названия столбцов к формату PEP8;
  • Обработали пропуски, аномалии и неверный тип данных: удалили столбцы где значения превышают 85%, убрали аномалии по отрицательной мощности и привели все временные данные к формату datetime;
  • Добавили дополнительные признаки там, где это показалось уместным.
  • Подготовили данные для объединения в общий датасет. Также удалили данные с одним измерением температуры.

Объединение данных¶

Для объединения датасетов используем все, кроме data_bulk_time_new и data_wire_time_new

In [64]:
data =  data_arc_new_gp.merge(data_temp_gp, on='key', how='inner')
data = data.merge(data_gas_new, on='key', how='inner')
data = data.merge(data_bulk_new, on='key', how='inner')
data = data.merge(data_wire_new, on='key', how='inner')
In [65]:
show_info(data)
key active_power reactive_power full_power heating_time first_time finish_time first_temp finish_temp time_diff gas bulk_3 bulk_4 bulk_6 bulk_12 bulk_14 bulk_15 wire_1 wire_2
0 1 3.036730 2.142821 3.718736 1098 2019-05-03 11:02:04 2019-05-03 11:30:38 1571.0 1613.0 1714 29.749986 NaN 43.0 NaN 206.0 150.0 154.0 60.059998 NaN
1 2 2.139408 1.453357 2.588349 811 2019-05-03 11:34:04 2019-05-03 11:55:09 1581.0 1602.0 1265 12.555561 NaN 73.0 NaN 206.0 149.0 154.0 96.052315 NaN
2 3 4.063641 2.937457 5.019223 655 2019-05-03 12:06:44 2019-05-03 12:35:57 1596.0 1599.0 1753 28.554793 NaN 34.0 NaN 205.0 152.0 153.0 91.160157 NaN
3 4 2.706489 2.056992 3.400038 741 2019-05-03 12:39:27 2019-05-03 12:59:47 1601.0 1625.0 1220 18.841219 NaN 81.0 NaN 207.0 153.0 154.0 89.063515 NaN
4 5 2.252950 1.687991 2.816980 869 2019-05-03 13:11:03 2019-05-03 13:36:39 1576.0 1602.0 1536 5.413692 NaN 78.0 NaN 203.0 151.0 152.0 89.238236 9.11456
<class 'pandas.core.frame.DataFrame'>
Int64Index: 2329 entries, 0 to 2328
Data columns (total 19 columns):
 #   Column          Non-Null Count  Dtype         
---  ------          --------------  -----         
 0   key             2329 non-null   int64         
 1   active_power    2329 non-null   float64       
 2   reactive_power  2329 non-null   float64       
 3   full_power      2329 non-null   float64       
 4   heating_time    2329 non-null   int64         
 5   first_time      2329 non-null   datetime64[ns]
 6   finish_time     2329 non-null   datetime64[ns]
 7   first_temp      2329 non-null   float64       
 8   finish_temp     2329 non-null   float64       
 9   time_diff       2329 non-null   int64         
 10  gas             2329 non-null   float64       
 11  bulk_3          960 non-null    float64       
 12  bulk_4          812 non-null    float64       
 13  bulk_6          438 non-null    float64       
 14  bulk_12         1812 non-null   float64       
 15  bulk_14         2068 non-null   float64       
 16  bulk_15         1699 non-null   float64       
 17  wire_1          2306 non-null   float64       
 18  wire_2          811 non-null    float64       
dtypes: datetime64[ns](2), float64(14), int64(3)
memory usage: 363.9 KB
========================================================================================================================
Количество дубликатов: 0
========================================================================================================================
Процент пропусков от всего датасета:

bulk_6            81.2%
wire_2            65.2%
bulk_4            65.1%
bulk_3            58.8%
bulk_15           27.1%
bulk_12           22.2%
bulk_14           11.2%
wire_1             1.0%
gas                0.0%
key                0.0%
active_power       0.0%
finish_temp        0.0%
first_temp         0.0%
finish_time        0.0%
first_time         0.0%
heating_time       0.0%
full_power         0.0%
reactive_power     0.0%
time_diff          0.0%
dtype: object
========================================================================================================================
Описание: 
key active_power reactive_power full_power heating_time first_temp finish_temp time_diff gas bulk_3 bulk_4 bulk_6 bulk_12 bulk_14 bulk_15 wire_1 wire_2
count 2329.000000 2329.000000 2329.000000 2329.000000 2329.000000 2329.000000 2329.000000 2329.000000 2329.000000 960.000000 812.000000 438.000000 1812.000000 2068.000000 1699.000000 2306.000000 811.000000
mean 1251.832546 3.125033 2.300522 3.884654 807.600687 1586.718763 1593.365393 2322.899957 11.375600 114.868750 106.995074 119.269406 267.880795 173.271277 164.432019 103.465371 50.571346
std 714.762400 1.221007 0.903968 1.518157 340.897332 28.290792 11.200915 1384.892092 6.392041 77.485694 49.050943 70.747953 125.588642 64.009860 50.039060 42.530971 39.755956
min 1.000000 0.267676 0.196228 0.331897 57.000000 1191.000000 1541.000000 339.000000 0.008399 6.000000 13.000000 17.000000 53.000000 29.000000 1.000000 1.918800 0.030160
25% 630.000000 2.293900 1.669572 2.843058 581.000000 1571.000000 1587.000000 1581.000000 7.282948 57.000000 73.000000 72.000000 204.000000 123.000000 105.000000 75.042236 20.193680
50% 1255.000000 3.035365 2.225398 3.767499 778.000000 1587.000000 1593.000000 2047.000000 10.100950 96.500000 105.000000 100.000000 208.000000 153.000000 200.000000 102.053638 40.112801
75% 1868.000000 3.834300 2.829159 4.769421 993.000000 1603.000000 1598.000000 2791.000000 14.216688 152.250000 136.250000 155.750000 359.250000 208.000000 205.000000 128.220310 69.699761
max 2499.000000 12.375636 8.949049 15.288271 4189.000000 1660.000000 1653.000000 23674.000000 77.995040 454.000000 281.000000 503.000000 1849.000000 636.000000 405.000000 330.314424 282.780152
========================================================================================================================
Размер: (2329, 19)

Вывод

Мы объединили все необходимые датасеты в один итоговый.

Исследовательский анализ и предобработка данных объединённого датафрейма

В объединенном датасете присутствуют пропуски в подаче материалов wire и bulk, заменим их на значение 0

In [66]:
data = data.fillna(0)
data.isna().sum()
Out[66]:
key               0
active_power      0
reactive_power    0
full_power        0
heating_time      0
first_time        0
finish_time       0
first_temp        0
finish_temp       0
time_diff         0
gas               0
bulk_3            0
bulk_4            0
bulk_6            0
bulk_12           0
bulk_14           0
bulk_15           0
wire_1            0
wire_2            0
dtype: int64

В результате заполнения пропуски в датасете исчезли

In [67]:
num_variables_col = data.select_dtypes(include=['number'])
data_corr = data.phik_matrix(interval_cols=num_variables_col)
plt.figure(figsize=(14, 10))
sns.heatmap(data_corr, annot=True, fmt=".2f", center=0)
plt.title('Матрица корреляций phik для объединенного датафрейма')
plt.show()
No description has been provided for this image

Удалим столбцы со временем first_time и finish_time вызывающую мультиколлинеарность, а также столбец c номером key не несущий смысловой нагрузки

In [68]:
data = data.drop(columns=['key', 'first_time', 'finish_time'])
In [69]:
num_variables_col = data.select_dtypes(include=['number'])
data_corr = data.phik_matrix(interval_cols=num_variables_col)
plt.figure(figsize=(14, 10))
sns.heatmap(data_corr, annot=True, fmt=".2f", center=0)
plt.title('Матрица корреляций phik для объединенного датафрейма')
plt.show()
No description has been provided for this image

Наблюдается высокая корреляция признаков наблюдается между: active_power, reactive_power, full_power, т.к. данные значения связаны физической формулой.

Для целевого признака finish_temp наибольшая корреляция заметна с параметрами active_power, full_power и wire_1.

In [70]:
# Вывод графиков для количественных признаков датафрейма
for col in num_variables_col:
    show_num_variable(data, col)
    print('=' * TERM_SIZE.columns)
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
No description has been provided for this image
========================================================================================================================
In [71]:
data.describe().round(3)
Out[71]:
active_power reactive_power full_power heating_time first_temp finish_temp time_diff gas bulk_3 bulk_4 bulk_6 bulk_12 bulk_14 bulk_15 wire_1 wire_2
count 2329.000 2329.000 2329.000 2329.000 2329.000 2329.000 2329.000 2329.000 2329.000 2329.000 2329.000 2329.000 2329.000 2329.000 2329.000 2329.000
mean 3.125 2.301 3.885 807.601 1586.719 1593.365 2322.900 11.376 47.348 37.304 22.430 208.416 153.854 119.953 102.444 17.610
std 1.221 0.904 1.518 340.897 28.291 11.201 1384.892 6.392 75.310 58.643 55.791 157.062 81.404 84.640 43.540 33.625
min 0.268 0.196 0.332 57.000 1191.000 1541.000 339.000 0.008 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
25% 2.294 1.670 2.843 581.000 1571.000 1587.000 1581.000 7.283 0.000 0.000 0.000 105.000 105.000 0.000 73.208 0.000
50% 3.035 2.225 3.767 778.000 1587.000 1593.000 2047.000 10.101 0.000 0.000 0.000 206.000 149.000 107.000 101.119 0.000
75% 3.834 2.829 4.769 993.000 1603.000 1598.000 2791.000 14.217 80.000 77.000 0.000 282.000 204.000 204.000 128.092 23.103
max 12.376 8.949 15.288 4189.000 1660.000 1653.000 23674.000 77.995 454.000 281.000 503.000 1849.000 636.000 405.000 330.314 282.780

Вывод по графикам

Вывод по графикам для параметров active_power, reactive_power, full_power, heating_time gas, time_diff и bulk_12:

  • На графике имеется один пик — большинство значений сосредоточено в узком диапазоне;
  • Длинный "хвост" тянется вправо т.е, иногда встречаются более крупные значения (выбросы), но они редкие;
  • Распределение не симметрично, а смещено вправо.

Вывод по графикам для параметра first_temp:

  • Распределение не симметрично, а смещено влево.
  • Значение медианы и среднего различаются менее чем на 1%.

Вывод по графикам для параметра finish_temp и wire_1:

  • График имеет нормальное распределение;
  • Значение медианы и среднего различаются менее чем на 1%.

Вывод по графикам для параметров bulk_3, bulk_4, bulk_6 и wire_2:

  • Графики имеют одномодальное распределение со значением близким к 0 и длинными "хвостами" тянущиеся вправо.

Вывод по графикам для параметра bulk_14 и bulk_15:

  • Графики имеют трехмодальное распределение с 3-мя выраженными пиками.

Подготовка данных¶

In [72]:
X = data.drop('finish_temp', axis=1)
y = data['finish_temp']

num_columns = X.select_dtypes(include=['number']).columns.tolist()
# Вывод списка столбцов с количественными данными
display(num_columns)

X_train, X_test, y_train, y_test = train_test_split(
    X, 
    y, 
    test_size=TEST_SIZE, 
    random_state=RANDOM_STATE
)
['active_power',
 'reactive_power',
 'full_power',
 'heating_time',
 'first_temp',
 'time_diff',
 'gas',
 'bulk_3',
 'bulk_4',
 'bulk_6',
 'bulk_12',
 'bulk_14',
 'bulk_15',
 'wire_1',
 'wire_2']
In [73]:
print('Размер X_train: ', X_train.shape)
print('Размер X_test: ', X_test.shape)
print('Размер y_train: ', y_train.shape)
print('Размер y_test: ', y_test.shape)
Размер X_train:  (1746, 15)
Размер X_test:  (583, 15)
Размер y_train:  (1746,)
Размер y_test:  (583,)

Вывод

Данные были подготовлены для обучения моделей и разделены в соотношении 3:1 в соответствии с заданием

Обучение моделей машинного обучения¶

In [74]:
# Обработка числовых и категориальных признаков с импутацией
num_transformer = Pipeline(steps=[
    ("imputer", SimpleImputer(strategy='constant', fill_value=0)), # Заполнение пропусков 0
    ("scaler", StandardScaler())
])

preprocessor = ColumnTransformer([
    ('num', num_transformer, num_columns),
])

pipe = Pipeline([
    ('preprocessor', preprocessor),
    ('model', LinearRegression())  
])
In [75]:
param_grid = [
    {
        'preprocessor__num__scaler': [StandardScaler(), MinMaxScaler(), 'passthrough'],
        'model': [DummyRegressor()],
        'model__strategy': ['mean', 'median']
    },
    {
        'preprocessor__num__scaler': [StandardScaler(), MinMaxScaler(), 'passthrough'],
        'model': [LinearRegression()],
    },
    {
        'preprocessor__num__scaler': ['passthrough'], # Для деревьев решений scaler не требуется
        'model': [RandomForestRegressor(
            random_state=RANDOM_STATE,
            n_jobs=-1
            )],
        'model__n_estimators': [50, 100, 200],
        'model__max_depth': [5, 10],
        'model__min_samples_split': [2, 5],
        'model__min_samples_leaf': [1, 2, 4],
        'model__criterion': ['absolute_error']
    },
    {
        'preprocessor__num__scaler': ['passthrough'], # Для деревьев решений scaler не требуется
        'model': [
            CatBoostRegressor(
                random_state=RANDOM_STATE,
                verbose=False,
                loss_function='MAE',
                iterations=1000,
                thread_count=multiprocessing.cpu_count(),
                early_stopping_rounds=50
            )
        ],
        'model__learning_rate': [0.01, 0.1]
    }
]
In [76]:
grid_search = GridSearchCV(
    pipe,
    param_grid,
    n_jobs=-1,
    cv=5,
    scoring='neg_mean_absolute_error'
)
In [77]:
grid_search.fit(X_train, y_train)
Out[77]:
GridSearchCV(cv=5,
             estimator=Pipeline(steps=[('preprocessor',
                                        ColumnTransformer(transformers=[('num',
                                                                         Pipeline(steps=[('imputer',
                                                                                          SimpleImputer(fill_value=0,
                                                                                                        strategy='constant')),
                                                                                         ('scaler',
                                                                                          StandardScaler())]),
                                                                         ['active_power',
                                                                          'reactive_power',
                                                                          'full_power',
                                                                          'heating_time',
                                                                          'first_temp',
                                                                          'time_diff',
                                                                          'gas',
                                                                          'bulk_3',
                                                                          'bulk_4',
                                                                          'bulk_6',
                                                                          'bulk_12',
                                                                          'bulk_14',
                                                                          'bulk_...
                          'model__max_depth': [5, 10],
                          'model__min_samples_leaf': [1, 2, 4],
                          'model__min_samples_split': [2, 5],
                          'model__n_estimators': [50, 100, 200],
                          'preprocessor__num__scaler': ['passthrough']},
                         {'model': [<catboost.core.CatBoostRegressor object at 0x00000257E6A95640>],
                          'model__learning_rate': [0.01, 0.1],
                          'preprocessor__num__scaler': ['passthrough']}],
             scoring='neg_mean_absolute_error')
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
GridSearchCV(cv=5,
             estimator=Pipeline(steps=[('preprocessor',
                                        ColumnTransformer(transformers=[('num',
                                                                         Pipeline(steps=[('imputer',
                                                                                          SimpleImputer(fill_value=0,
                                                                                                        strategy='constant')),
                                                                                         ('scaler',
                                                                                          StandardScaler())]),
                                                                         ['active_power',
                                                                          'reactive_power',
                                                                          'full_power',
                                                                          'heating_time',
                                                                          'first_temp',
                                                                          'time_diff',
                                                                          'gas',
                                                                          'bulk_3',
                                                                          'bulk_4',
                                                                          'bulk_6',
                                                                          'bulk_12',
                                                                          'bulk_14',
                                                                          'bulk_...
                          'model__max_depth': [5, 10],
                          'model__min_samples_leaf': [1, 2, 4],
                          'model__min_samples_split': [2, 5],
                          'model__n_estimators': [50, 100, 200],
                          'preprocessor__num__scaler': ['passthrough']},
                         {'model': [<catboost.core.CatBoostRegressor object at 0x00000257E6A95640>],
                          'model__learning_rate': [0.01, 0.1],
                          'preprocessor__num__scaler': ['passthrough']}],
             scoring='neg_mean_absolute_error')
Pipeline(steps=[('preprocessor',
                 ColumnTransformer(transformers=[('num',
                                                  Pipeline(steps=[('imputer',
                                                                   SimpleImputer(fill_value=0,
                                                                                 strategy='constant')),
                                                                  ('scaler',
                                                                   'passthrough')]),
                                                  ['active_power',
                                                   'reactive_power',
                                                   'full_power', 'heating_time',
                                                   'first_temp', 'time_diff',
                                                   'gas', 'bulk_3', 'bulk_4',
                                                   'bulk_6', 'bulk_12',
                                                   'bulk_14', 'bulk_15',
                                                   'wire_1', 'wire_2'])])),
                ('model',
                 <catboost.core.CatBoostRegressor object at 0x00000257E6C43610>)])
ColumnTransformer(transformers=[('num',
                                 Pipeline(steps=[('imputer',
                                                  SimpleImputer(fill_value=0,
                                                                strategy='constant')),
                                                 ('scaler', 'passthrough')]),
                                 ['active_power', 'reactive_power',
                                  'full_power', 'heating_time', 'first_temp',
                                  'time_diff', 'gas', 'bulk_3', 'bulk_4',
                                  'bulk_6', 'bulk_12', 'bulk_14', 'bulk_15',
                                  'wire_1', 'wire_2'])])
['active_power', 'reactive_power', 'full_power', 'heating_time', 'first_temp', 'time_diff', 'gas', 'bulk_3', 'bulk_4', 'bulk_6', 'bulk_12', 'bulk_14', 'bulk_15', 'wire_1', 'wire_2']
SimpleImputer(fill_value=0, strategy='constant')
passthrough
<catboost.core.CatBoostRegressor object at 0x00000257E6C43610>

Вывод

На тренировочной выборке были обучены 4 модели: DummyRegressor(), LinearRegression(), RandomForestRegressor() и CatBoostRegressor().

Выбор лучшей модели¶

In [78]:
# Вывод результатов
print(f"Лучший результат MAE: {-grid_search.best_score_:.2f}")
print("Лучшие параметры:", grid_search.best_params_)
Лучший результат MAE: 5.97
Лучшие параметры: {'model': <catboost.core.CatBoostRegressor object at 0x00000257E6A95640>, 'model__learning_rate': 0.01, 'preprocessor__num__scaler': 'passthrough'}

Лучший результат MAE = 5.97 показала модель CatBoostRegressor() с гиперпараметрами 'model__learning_rate': 0.01, 'preprocessor__num__scaler': 'passthrough'.
Используем ее для предсказания на тестовой выборке

In [79]:
best_model = grid_search.best_estimator_
y_pred = best_model.predict(X_test)
mae = mean_absolute_error(y_test, y_pred)
print(f"✅ MAE на тестовых данных: {mae:.2f}")
✅ MAE на тестовых данных: 6.50

Метрика МАЕ=6.5 на тестовой выборке, что удовлетворяет условию задачи (менее 6.8)

Проверка на адекватность¶

Проверим данную модель на адекватность сравнив её с моделью DummyRegressor на тестовой выборке, модель делает предсказание как среднее или медианное значение

In [80]:
dummy_grid = [
    {
        'preprocessor__num__scaler': [StandardScaler(), MinMaxScaler(), 'passthrough'],
        'model': [DummyRegressor()],
        'model__strategy': ['mean', 'median']
    }
]
In [81]:
grid_search_dummy = GridSearchCV(
    pipe,
    dummy_grid,
    n_jobs=-1,
    cv=5,
    scoring='neg_mean_absolute_error'
)
In [82]:
grid_search_dummy.fit(X_train, y_train)
Out[82]:
GridSearchCV(cv=5,
             estimator=Pipeline(steps=[('preprocessor',
                                        ColumnTransformer(transformers=[('num',
                                                                         Pipeline(steps=[('imputer',
                                                                                          SimpleImputer(fill_value=0,
                                                                                                        strategy='constant')),
                                                                                         ('scaler',
                                                                                          StandardScaler())]),
                                                                         ['active_power',
                                                                          'reactive_power',
                                                                          'full_power',
                                                                          'heating_time',
                                                                          'first_temp',
                                                                          'time_diff',
                                                                          'gas',
                                                                          'bulk_3',
                                                                          'bulk_4',
                                                                          'bulk_6',
                                                                          'bulk_12',
                                                                          'bulk_14',
                                                                          'bulk_15',
                                                                          'wire_1',
                                                                          'wire_2'])])),
                                       ('model', LinearRegression())]),
             n_jobs=-1,
             param_grid=[{'model': [DummyRegressor()],
                          'model__strategy': ['mean', 'median'],
                          'preprocessor__num__scaler': [StandardScaler(),
                                                        MinMaxScaler(),
                                                        'passthrough']}],
             scoring='neg_mean_absolute_error')
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
GridSearchCV(cv=5,
             estimator=Pipeline(steps=[('preprocessor',
                                        ColumnTransformer(transformers=[('num',
                                                                         Pipeline(steps=[('imputer',
                                                                                          SimpleImputer(fill_value=0,
                                                                                                        strategy='constant')),
                                                                                         ('scaler',
                                                                                          StandardScaler())]),
                                                                         ['active_power',
                                                                          'reactive_power',
                                                                          'full_power',
                                                                          'heating_time',
                                                                          'first_temp',
                                                                          'time_diff',
                                                                          'gas',
                                                                          'bulk_3',
                                                                          'bulk_4',
                                                                          'bulk_6',
                                                                          'bulk_12',
                                                                          'bulk_14',
                                                                          'bulk_15',
                                                                          'wire_1',
                                                                          'wire_2'])])),
                                       ('model', LinearRegression())]),
             n_jobs=-1,
             param_grid=[{'model': [DummyRegressor()],
                          'model__strategy': ['mean', 'median'],
                          'preprocessor__num__scaler': [StandardScaler(),
                                                        MinMaxScaler(),
                                                        'passthrough']}],
             scoring='neg_mean_absolute_error')
Pipeline(steps=[('preprocessor',
                 ColumnTransformer(transformers=[('num',
                                                  Pipeline(steps=[('imputer',
                                                                   SimpleImputer(fill_value=0,
                                                                                 strategy='constant')),
                                                                  ('scaler',
                                                                   StandardScaler())]),
                                                  ['active_power',
                                                   'reactive_power',
                                                   'full_power', 'heating_time',
                                                   'first_temp', 'time_diff',
                                                   'gas', 'bulk_3', 'bulk_4',
                                                   'bulk_6', 'bulk_12',
                                                   'bulk_14', 'bulk_15',
                                                   'wire_1', 'wire_2'])])),
                ('model', DummyRegressor(strategy='median'))])
ColumnTransformer(transformers=[('num',
                                 Pipeline(steps=[('imputer',
                                                  SimpleImputer(fill_value=0,
                                                                strategy='constant')),
                                                 ('scaler', StandardScaler())]),
                                 ['active_power', 'reactive_power',
                                  'full_power', 'heating_time', 'first_temp',
                                  'time_diff', 'gas', 'bulk_3', 'bulk_4',
                                  'bulk_6', 'bulk_12', 'bulk_14', 'bulk_15',
                                  'wire_1', 'wire_2'])])
['active_power', 'reactive_power', 'full_power', 'heating_time', 'first_temp', 'time_diff', 'gas', 'bulk_3', 'bulk_4', 'bulk_6', 'bulk_12', 'bulk_14', 'bulk_15', 'wire_1', 'wire_2']
SimpleImputer(fill_value=0, strategy='constant')
StandardScaler()
DummyRegressor(strategy='median')
In [83]:
# Вывод результатов
print(f"Лучший результат MAE: {-grid_search_dummy.best_score_:.2f}")
print("Лучшие параметры:", grid_search_dummy.best_params_)
Лучший результат MAE: 7.92
Лучшие параметры: {'model': DummyRegressor(), 'model__strategy': 'median', 'preprocessor__num__scaler': StandardScaler()}

На обучающей выборке модель DummyRegressor() с медианными значениями и масштабированием с помощью StandardScaler() показала метрику МАЕ=7.92

In [84]:
dummy_model = grid_search_dummy.best_estimator_
y_pred_dummy = dummy_model.predict(X_test)
mae_dummy = mean_absolute_error(y_test, y_pred_dummy)
print(f"✅ MAE на тестовых данных: {mae_dummy:.2f}")
✅ MAE на тестовых данных: 8.54

Модель предсказывающая медиану финвльной температуры показала метрику MAE=8.54 на тестовой выборке, что больше чем модель CatBoostRegressor() с MAE=6.5.

Это подтверждает адекватность и целесообразность использования для предсказания финальной температуры после обработки материала CatBoostRegressor().

Анализ важности признаков для лучшей модели¶

In [85]:
catboost_model = best_model.named_steps['model']
feature_names = best_model.named_steps['preprocessor'].transformers_[0][2]

feature_importance_data = pd.DataFrame({
    'feature': feature_names,
    'importance': catboost_model.feature_importances_
})

feature_importance_data = feature_importance_data.sort_values(by='importance', ascending=False)

plt.figure(figsize=(12, 8))
sns.barplot(x='importance', y='feature', data=feature_importance_data)
plt.title('Важность признаков для CatBoostRegressor', fontsize=16)
plt.xlabel('Важность признака')
plt.ylabel('Входной признак')
plt.tight_layout()
plt.show()
No description has been provided for this image

Для модели наиболее важными признаками являются:

  • время требующееся на нагрев;
  • начальная температура материала.

Вывол

  • Была выбрана лучшая модель - CatBoostRegressor с метрикой MAE=6.5 на тестовой выборке;
  • Была произведена проверка лучшей модели на адекватность путем ее сравнения с моделью DummyRegressor на тестовой выборке;
  • Были определены наиболее важные входные признаки для предсказания моделью целевого признака finish_temp (конечная температура после обработки).

Общий вывод¶

  1. Были загружены и проанализированы полученные датасеты.
  2. В ходе предобработки и исследовательского анализа датасетов:
    • названия столбцов датасетов было приведено к формату PEP8;
    • столбцы с временными данными были приведены к формату datetime;
    • столбцы с большим количеством пропусков (более 85%) были убраны из датасетов;
    • необходимые датасеты были сгруппированы по партиям;
    • были убраны партии с аномальными значениями реактивной мощности < 0;
    • были созданы новые входные признаки из данных находящихся в датасете;
    • был проведен статистический анализ датасетов и сделаны выводы.
  3. Была создана объединенная таблица, в которой использовали все датасеты, кроме data_bulk_time_new и data_wire_time_new. Итоговый размер датасета (2329, 19).
  4. Была произведена предобработка датасета и его анализ в ходе которых:
    • все пропуски в столбцах подачи материалов заменены на 0;
    • были удалены столбцы key, first_time и finish_time не несущие смысловой нагрузки или создающие мультиколлинеарность в датасете;
    • был проведен корреляционный и статистический анализ итогового датасета для машинного обучения.
  5. Были подготовлены данные для МО, датасет был разделен на тренировочную и тестовую выборку в соотношении 3:1.
  6. Были обучены 4 модели машинного обучения: DummyRegressor, LinearRegression, RandomForestRegressor и CatBoostRegressor. И подобраны гиперпараметры с помощью GridSearchCV.
  7. Был произведен выбор лучшей модели и ее анализ:
    • в ходе подбора гиперпараметров с помощью GridSearchCV была определена лучшая модель на обучающей выборке - ей оказалась CatBoostRegressor с гиперпараметрами ('model__learning_rate': 0.01, 'preprocessor__num__scaler': 'passthrough') и метрикой качества MAE=5.97;
    • модель CatBoostRegressor показала удволетворяющее условию качество на тестовой выборке MAE=6.5 (менее 6.8);
    • модель CatBoostRegressor была проверена на адекватность путем сравнения ее метрики качества МАЕ на тестовой выборке с констанстной моделью DummyRegressor. Метрика качества МАЕ для модели CatBoostRegressor оказалась ниже (6.5 против 8.54). Что подтвердило адекватность и целесообразность использования для предсказания финальной температуры после обработки материала полученную модель CatBoostRegressor();
    • был проведен анализ важности входных признаков на предсказание модели, наиболее важными признаками являются: heating_time (время требующееся на нагрев) и first_temp начальная температура материала.

Рекомендация в ходе проделанной работы - уделить внимание точности и своевременности замеров температуры.